#!/usr/bin/env bash
# pfiles
# Copyright (C) 2007-2008 Red Hat, Inc., Eugene Teo <eteo@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# pfiles report information for all open files by the process id.
#
# pfiles is not a port of Solaris' pfiles tool. It was written based on the
# example outputs in:
# - https://bugzilla.redhat.com/show_bug.cgi?id=223489
# - http://blogs.sun.com/peteh/entry/pfiles_1_locked_files_and
# - http://lists.samba.org/archive/samba-technical/2001-May/014293.html
# - http://mail.gnome.org/archives/gconf-list/2004-December/msg00005.html
#
# pfiles is able to:
# - report locked open files
# - report pathname information
# - report socket information (thanks Luis Henriques)
#
# Last updated: Sat Jan 19 22:40:15 SGT 2008
#
# $ pfiles.sh $$ | head -5
# 30527: bash
#  Current rlimit: 256 file descriptors
#   0: S_IFCHR mode:0620 dev:0,11 ino:9 uid:500 gid:500 rdev:136,7
#      O_RDWR 
#      /dev/pts/7
# $ pfiles `pgrep mugshot` | tail -12
#   8: S_IFREG mode:0644 dev:253,0 ino:16130552 uid:500 gid:500 rdev:0,0
#      O_RDWR|O_LARGEFILE FD_CLOEXEC
#      /home/eteo/.online-data-cache/mugshot.org:80-9pfDXd0WvvZwJS-e0e6b0fcc761245bae6f5600477xxxxx.db
#   9: S_IFSOCK mode:0777 dev:0,5 ino:40773 uid:500 gid:500 rdev:0,0
#      O_RDWR|O_NONBLOCK 
#      socket:[40773]
#        sockname: AF_INET 192.168.1.x  port: 44357
#  10: S_IFSOCK mode:0777 dev:0,5 ino:40788 uid:500 gid:500 rdev:0,0
#      O_RDWR|O_NONBLOCK 
#      socket:[40788]
#        sockname: AF_INET 192.168.1.x  port: 49728
#        peername: AF_INET 72.32.117.234  port: 5222
#
# I used Andrew Tridge1ll's locktst.c to test the advisory lock code.
# You can download it at http://samba.org/ftp/unpacked/junkcode/locktst.c
#
# $ ./locktst file &
# fcntl_lock 3 6 1 1 1
# $ pfiles 10126 | grep advisory -A1 -B2
#   3: S_IFREG mode:0644 dev:253,0 ino:15237159 uid:500 gid:500 rdev:0,0
#      O_RDWR 
#      advisory write lock set by process 10126
#      /home/eteo/pfiles/file
#

if [ $# -eq 0 ]; then
	cat >&2 <<-EOF
	usage:  pfiles pid ...
	  (report open files of each process)
	EOF
	exit
fi

pid=$1

/usr/bin/env stap -DMAXACTION=50000 -g -e '
%{
#include <linux/file.h>
#include <net/sock.h>
%}

function i_mode_str:string (i_mode:long) %{ /* pure */
	if (S_ISLNK(THIS->i_mode)) strlcpy (THIS->__retvalue, "S_IFLNK", MAXSTRINGLEN);
	else if (S_ISREG(THIS->i_mode)) strlcpy (THIS->__retvalue, "S_IFREG", MAXSTRINGLEN);
	else if (S_ISDIR(THIS->i_mode)) strlcpy (THIS->__retvalue, "S_IFDIR", MAXSTRINGLEN);
	else if (S_ISCHR(THIS->i_mode)) strlcpy (THIS->__retvalue, "S_IFCHR", MAXSTRINGLEN);
	else if (S_ISBLK(THIS->i_mode)) strlcpy (THIS->__retvalue, "S_IFBLK", MAXSTRINGLEN);
	else if (S_ISFIFO(THIS->i_mode)) strlcpy (THIS->__retvalue, "S_IFIFO", MAXSTRINGLEN);
	else if (S_ISSOCK(THIS->i_mode)) strlcpy (THIS->__retvalue, "S_IFSOCK", MAXSTRINGLEN);
%}

function get_task_struct:long (pid:long) %{
	struct task_struct *p;

	rcu_read_lock();
	p = find_task_by_pid((int)THIS->pid);
	rcu_read_unlock();

	THIS->__retvalue = (long)kread(&p);
	CATCH_DEREF_FAULT();
%}

function get_task_info:string (task:long) %{
	int i;
	char pid[10], comm[TASK_COMM_LEN];
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	for (i = 0; i < TASK_COMM_LEN; ++i)
		comm[i] = kread(&p->comm[i]);
	sprintf(pid, "%d:", kread(&p->pid));
	snprintf(THIS->__retvalue, MAXSTRINGLEN, "%-6s %s", pid, comm);
	CATCH_DEREF_FAULT();
%}

function get_max_fds_info:long (task:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct fdtable *fdt;

	spin_lock(&files->file_lock);
	fdt = files_fdtable(files);
	THIS->__retvalue = kread(&fdt->max_fds);
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_curr_rlimit:string (task:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct fdtable *fdt;

	spin_lock(&files->file_lock);
	fdt = files_fdtable(files);
	snprintf(THIS->__retvalue, MAXSTRINGLEN,
			"  Current rlimit: %d file descriptors",
			kread(&fdt->max_fds));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_dev_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	int dev_nr, rdev_nr;
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);

	dev_nr = kread(&filp->f_dentry->d_inode->i_sb->s_dev);

	snprintf(THIS->__retvalue, MAXSTRINGLEN,
			"%d,%d", MAJOR(dev_nr), MINOR(dev_nr));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_flock_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct file_lock *flock;
	int fl_type, fl_pid;
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	flock = kread(&filp->f_path.dentry->d_inode->i_flock);
	fl_type = fl_pid = -1;
	if (flock) {
		fl_type = kread(&flock->fl_type);
		fl_pid = kread(&flock->fl_pid);
	}

	if (fl_type != -1) /* !flock */
		snprintf(THIS->__retvalue, MAXSTRINGLEN,
				"      advisory %s lock set by process %d",
				fl_type ? "write" : "read", fl_pid);
	else
		snprintf(THIS->__retvalue, MAXSTRINGLEN, "NULL");
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_rdev_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	int dev_nr, rdev_nr;
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);

	rdev_nr = kread(&filp->f_dentry->d_inode->i_rdev);

	snprintf(THIS->__retvalue, MAXSTRINGLEN,
			"%d,%d", MAJOR(rdev_nr), MINOR(rdev_nr));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_i_mode_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	snprintf(THIS->__retvalue, MAXSTRINGLEN,
			"%d", kread(&filp->f_dentry->d_inode->i_mode));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_i_ino_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	snprintf(THIS->__retvalue, MAXSTRINGLEN,
			"%lu", kread(&filp->f_dentry->d_inode->i_ino));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_uid_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	snprintf(THIS->__retvalue, MAXSTRINGLEN, "%d", kread(&filp->f_uid));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_gid_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	snprintf(THIS->__retvalue, MAXSTRINGLEN, "%d", kread(&filp->f_gid));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_fd_info(task, fd) {
	return sprintf("%4d: %s mode:%04o dev:%s ino:%d uid:%s gid:%s rdev:%s",
			fd, i_mode_str(strtol(get_i_mode_info(task, fd), 10)),
			strtol(get_i_mode_info(task, fd), 10) & 0777,
			get_dev_info(task, fd),
			strtol(get_i_ino_info(task, fd), 10),
			get_uid_info(task, fd),
			get_gid_info(task, fd),
			get_rdev_info(task, fd));
}

function get_flags_info(task, fd) {
	return sprintf("      %s %s",
			_sys_open_flag_str(strtol(get_f_flags_info(task, fd), 10)),
			get_fd_flag_info(task, fd));
}

function get_fd_flag_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct fdtable *fdt;
	int gcoe;

	spin_lock(&files->file_lock);
	fdt = files_fdtable(files);
	gcoe = FD_ISSET(THIS->fd, kread(&fdt->close_on_exec));
	snprintf(THIS->__retvalue, MAXSTRINGLEN,
			"%s", gcoe ? "FD_CLOEXEC" : "");
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_f_flags_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	snprintf(THIS->__retvalue, MAXSTRINGLEN, "%d", kread(&filp->f_flags));
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_d_path_info:string (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	char *page = (char *)__get_free_page(GFP_KERNEL);
	struct file *filp;
	struct dentry *dentry;
	struct vfsmount *vfsmnt;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	dentry = kread(&filp->f_path.dentry);
	vfsmnt = kread(&filp->f_path.mnt);
	snprintf(THIS->__retvalue, MAXSTRINGLEN, "      %s",
			d_path(dentry, vfsmnt, page, PAGE_SIZE));
	free_page((unsigned long)page);
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function is_fd_valid:long (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	if (!filp) {
		THIS->__retvalue = 0;
		spin_unlock(&files->file_lock);
		return;
	}
	THIS->__retvalue = 1;
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function is_socket:long (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct inode *inode;
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	THIS->__retvalue = 0;
	if (!filp) {
		spin_unlock(&files->file_lock);
		return;
	}
	inode = kread(&filp->f_path.dentry->d_inode);
	if (S_ISSOCK(kread(&inode->i_mode)))
		THIS->__retvalue = 1;
	spin_unlock(&files->file_lock);
	CATCH_DEREF_FAULT();
%}

function get_sockname_info:string (sock:long) %{
	struct socket *sock = (struct socket *)((long)THIS->sock);
	struct sockaddr_in in_addr;
	__be32 addr, port;
	int err, len;

	err = kernel_getsockname(kread(&sock), (struct sockaddr *)(&in_addr), &len);
	if (!err) {
		addr = kread(&in_addr.sin_addr.s_addr);
		port = htons(kread(&in_addr.sin_port));
		snprintf(THIS->__retvalue, MAXSTRINGLEN, "        sockname: AF_INET " NIPQUAD_FMT "  port: %d",
				NIPQUAD(addr), port);
	}
	CATCH_DEREF_FAULT();
%}

function get_peername_info:string (sock:long) %{
	struct socket *sock = (struct socket *)((long)THIS->sock);
	struct sockaddr_in in_addr;
	__be32 addr, port;
	int err, len;

	err = kernel_getpeername(kread(&sock), (struct sockaddr *)(&in_addr), &len);
	if (!err) {
		addr = kread(&in_addr.sin_addr.s_addr);
		port = htons(kread(&in_addr.sin_port));
		snprintf(THIS->__retvalue, MAXSTRINGLEN, "        peername: AF_INET " NIPQUAD_FMT "  port: %d",
				NIPQUAD(addr), port);
	}
	CATCH_DEREF_FAULT();
%}

function get_ipv6_sockname_info:string (sock:long) %{
	struct socket *sock = (struct socket *)((long)THIS->sock);
	struct sockaddr_in6 in_addr;
	__be32 port;
	int err, len;

	err = kernel_getsockname(kread(&sock), (struct sockaddr *)(&in_addr), &len);
	if (!err) {
		port = htons(kread(&in_addr.sin6_port));
		snprintf(THIS->__retvalue, MAXSTRINGLEN, "        sockname: AF_INET6 " NIP6_FMT "  port: %d",
				NIP6(in_addr.sin6_addr), port);
	}
	CATCH_DEREF_FAULT();
%}

function get_ipv6_peername_info:string (sock:long) %{
	struct socket *sock = (struct socket *)((long)THIS->sock);
	struct sockaddr_in6 in_addr;
	__be32 port;
	int err, len;

	err = kernel_getpeername(kread(&sock), (struct sockaddr *)(&in_addr), &len);
	if (!err) {
		port = htons(kread(&in_addr.sin6_port));
		snprintf(THIS->__retvalue, MAXSTRINGLEN, "        peername: AF_INET6 " NIP6_FMT "  port: %d",
				NIP6(in_addr.sin6_addr), port);
	}
	CATCH_DEREF_FAULT();
%}

function get_unix_sockname_info:string (sock:long) %{
	struct socket *sock = (struct socket *)((long)THIS->sock);
	struct sockaddr_un un_addr;
	int err, len;

	err = kernel_getsockname(kread(&sock), (struct sockaddr *)(&un_addr), &len);
	if (!err) {
		if (kread(&un_addr.sun_path[0]) != 0)
			snprintf(THIS->__retvalue, MAXSTRINGLEN,
				"        sockname: AF_UNIX %s", un_addr.sun_path);
		else
			snprintf(THIS->__retvalue, MAXSTRINGLEN, "        sockname: AF_UNIX");
	}
	CATCH_DEREF_FAULT();
%}

function get_unix_peername_info:string (sock:long) %{
	struct socket *sock = (struct socket *)((long)THIS->sock);
	struct sockaddr_un un_addr;
	int err, len;

	err = kernel_getpeername(kread(&sock), (struct sockaddr *)(&un_addr), &len);
	if (!err) {
		if (kread(&un_addr.sun_path[0]) != 0)
			snprintf(THIS->__retvalue, MAXSTRINGLEN,
				"        peername: AF_UNIX %s", un_addr.sun_path);
		else
			snprintf(THIS->__retvalue, MAXSTRINGLEN, "        peername: AF_UNIX");
	}
	CATCH_DEREF_FAULT();
%}

function get_socket_struct:long (task:long, fd:long) %{
	struct task_struct *p = (struct task_struct *)((long)THIS->task);
	struct files_struct *files = kread(&p->files);
	struct inode *inode;
	struct socket *sock;
	struct file *filp;

	spin_lock(&files->file_lock);
	filp = fcheck_files(files, THIS->fd);
	if (!filp) {
		spin_unlock(&files->file_lock);
		return;
	}
	spin_unlock(&files->file_lock);
	inode = kread(&filp->f_path.dentry->d_inode);
	sock = SOCKET_I(inode);
	THIS->__retvalue = (long)kread(&sock);
	CATCH_DEREF_FAULT();
%}

function get_family_info:long (sock:long) %{
	struct socket *sock = (struct socket *)((long)THIS->sock);
	THIS->__retvalue = (long)kread(&sock->ops->family);
	CATCH_DEREF_FAULT();
%}

probe begin {
	pid = '$pid'

	task = get_task_struct(pid)
     	printf("%s\n", get_task_info(task))
	printf("%s\n", get_curr_rlimit(task))

	max_fds = get_max_fds_info(task)
	for (fd = 0; fd < max_fds; fd++)
		if (is_fd_valid(task, fd)) {
			printf("%s\n", get_fd_info(task, fd))
			printf("%s\n", get_flags_info(task, fd))
			flock = get_flock_info(task, fd)
			if (!isinstr(flock, "NULL"))
				printf("%s\n", flock)
			printf("%s\n", get_d_path_info(task, fd))
			if (is_socket(task, fd)) {
				sock = get_socket_struct(task, fd)
				fam = get_family_info(sock)
				if (fam == 1) { /* AF_UNIX */
					sockname = get_unix_sockname_info(sock)
					peername = get_unix_peername_info(sock)
					printf("%s%s", strlen(sockname) > 0 ? sockname . "\n" : "",
						strlen(peername) > 0 ? peername . "\n" : "")
				} else if (fam == 2) { /* AF_INET */
					sockname = get_sockname_info(sock)
					peername = get_peername_info(sock)
					printf("%s%s", strlen(sockname) > 0 ? sockname . "\n" : "",
						strlen(peername) > 0 ? peername . "\n" : "")
				} else if (fam == 10) { /* AF_INET6 */
					sockname = get_ipv6_sockname_info(sock)
					peername = get_ipv6_peername_info(sock)
					printf("%s%s", strlen(sockname) > 0 ? sockname . "\n" : "",
						strlen(peername) > 0 ? peername . "\n" : "")
				}
			}
		}
	exit()
}
'
